﻿using System;
using System.Linq;
using System.Windows;
using System.Collections.Generic;
using System.Windows.Controls.Primitives;
using System.Collections;
using System.ComponentModel;
using System.Windows.Markup;
using System.Windows.Controls;
using System.Windows.Data;
using System.Globalization;
using System.Collections.ObjectModel;

namespace PasswordProvider.View.Library
{
    public enum EnumSelectorMode
    {
        /// <summary>
        /// Values are displayed inside a list box. Flags property determines if
        /// multiselect is enabled.
        /// </summary>
        ListBox,
        /// <summary>
        /// Values are displayed inside a combo box. Flags property is always false.
        /// </summary>
        ComboBox
    }

    /// <summary>
    /// Encapsulates the display name, tool tip and value of a single value from an enumerated type.
    /// A list of these is required for EnumSelector, one for each enumerated value that you want to show
    /// up in the list.
    /// </summary>
    public class EnumSelectorDisplayItem : DependencyObject
    {
        public EnumSelectorDisplayItem() { }
        public EnumSelectorDisplayItem(string displayName, Enum value) 
            : this(displayName, null, value)
        { }

        public EnumSelectorDisplayItem(string displayName, string toolTip, Enum value)
        {
            EnumValue = value;
            DisplayName = displayName;
            ToolTip = toolTip;
        }

        public static readonly DependencyProperty EnumValueProperty =
            DependencyProperty.Register("EnumValue", typeof(Enum), typeof(EnumSelectorDisplayItem));
        public Enum EnumValue 
        {
            get { return (Enum)GetValue(EnumValueProperty); }
            set { SetValue(EnumValueProperty, value); }
        }

        public static readonly DependencyProperty DisplayNameProperty =
            DependencyProperty.Register("DisplayName", typeof(string), typeof(EnumSelectorDisplayItem));
        public string DisplayName 
        {
            get { return (string)GetValue(DisplayNameProperty); }
            set { SetValue(DisplayNameProperty, value); }
        }

        public static readonly DependencyProperty ToolTipProperty =
            DependencyProperty.Register("ToolTip", typeof(string), typeof(EnumSelectorDisplayItem));
        public string ToolTip 
        {
            get { return (string)GetValue(ToolTipProperty); }
            set { SetValue(ToolTipProperty, value); }
        }        
    }

    /// <summary>
    /// Custom control for and selecting a value of an enumerated type property. Supports two way data binding,
    /// multi select of enumerated types which can be treated as flags, and display in either a list box or drop
    /// down combo box.
    /// </summary>
    [ContentProperty("DisplayItems")]
    public partial class EnumSelector : UserControl
    {
        public EnumSelector()
        {
            InitializeComponent();

            Loaded += delegate
            {
                if (EnumValue == null)
                    return; // Seems to get called twice, the first time is not fully loaded...

                SelectorObject.SelectionChanged += delegate { UpdateEnumValueAfterListBoxSelectionChange(); };
                if (TreatAsFlags && Mode == EnumSelectorMode.ListBox)
                {
                    (SelectorObject as ListBox).SelectionMode = SelectionMode.Multiple;
                }
                SetListBoxSelectedItemsAfterEnumValueChange(EnumValue);
            };
        }

        #region Dependency Properties

        /// <summary>
        /// Dependency property to set the display entries via data binding.
        /// </summary>
        public static readonly DependencyProperty DisplayItemsProperty =
           DependencyProperty.Register("DisplayItems", typeof(Collection<EnumSelectorDisplayItem>), typeof(EnumSelector));           
        /// <summary>
        /// Get or set the DisplayEntries, a collection of EnumDisplayEntry objects which provide display string information for 
        /// the values of the enumerated type. This is a dependency property.
        /// </summary>
        public Collection<EnumSelectorDisplayItem> DisplayItems
        {
            get
            {
                Collection<EnumSelectorDisplayItem> items = (Collection<EnumSelectorDisplayItem>)GetValue(DisplayItemsProperty);
                if (items == null)
                {
                    items = new Collection<EnumSelectorDisplayItem>();
                    SetValue(DisplayItemsProperty, items);
                }
                return items;            
            }
            //set
            //{
            //    SetValue(DisplayItemsProperty, value);
            //}
        }

        /// <summary>
        /// EnumTypeProperty: Dependency Property
        /// </summary>
        public static readonly DependencyProperty EnumTypeProperty =
           DependencyProperty.Register("EnumType", typeof(Type), typeof(EnumSelector));
        /// <summary>
        /// Get or set the enum type being displayed. The value for this property will be infered when EnumValue is set.
        /// </summary>
        public Type EnumType
        {
            get { return (Type)GetValue(EnumTypeProperty); }
            set { SetValue(EnumTypeProperty, value); }
        }

        /// <summary>
        /// EnumValueProperty: Dependency Property
        /// </summary>
        public static readonly DependencyProperty EnumValueProperty =
           DependencyProperty.Register("EnumValue", typeof(Enum), typeof(EnumSelector),
           new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
           (d, e) =>
           {
               if (e.NewValue != null)
               {
                   EnumSelector s = (EnumSelector)d;
                   s.EnumType = e.NewValue.GetType();
                   if (s.SelectorObject != null && s.SelectorObject.ItemsSource != null)
                       s.SetListBoxSelectedItemsAfterEnumValueChange((Enum)e.NewValue);
               }
           }));
        /// <summary>
        /// Get or set the current enum value. 
        /// </summary>
        public Enum EnumValue
        {
            get { return (Enum)GetValue(EnumValueProperty); }
            set { SetValue(EnumValueProperty, value); }
        }
        
        /// <summary>
        /// FlagsProperty: Dependency Property
        /// </summary>
        public static readonly DependencyProperty TreatAsFlagsProperty =
            DependencyProperty.Register("TreatAsFlags", typeof(bool), typeof(EnumSelector));
        /// <summary>
        /// Get or set whether the enum should be treated as a flag. Enables multi-select. Ignored if Mode is ComboBox.
        /// </summary>
        public bool TreatAsFlags
        {
            get { return (bool)GetValue(TreatAsFlagsProperty); }
            set { SetValue(TreatAsFlagsProperty, value); }
        }

        /// <summary>
        /// ModeProperty: Dependency property. 
        /// </summary>
        public static readonly DependencyProperty ModeProperty =
            DependencyProperty.Register("Mode", typeof(EnumSelectorMode), typeof(EnumSelector));
        /// <summary>
        /// Get or set whether to display items in a list box or a combo box. Note, if value
        /// is ComboBox, then the Flags property is ignored (will function as single select.) 
        /// This is a dependency property.
        /// </summary>
        public EnumSelectorMode Mode
        {
            get { return (EnumSelectorMode)GetValue(ModeProperty); }
            set { SetValue(ModeProperty, value); }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Gets the template-defined Selector object being used in the current mode - it will be either
        /// a ListBox or a ComboBox.
        /// </summary>
        private Selector SelectorObject
        {
            get
            {
                if (_selector == null)
                {
                    ControlTemplate template = this.Template;
                    _selector = template.FindName("SelectorObject", this) as Selector;
                    _selector.DisplayMemberPath = "DisplayName";
                }
                return _selector;
            }
        } private Selector _selector;


        // Flags to prevent infinite loop
        private bool _settingListBoxSelectedItems = false;
        private bool _updatingEnumValueAfterListBoxSelectionChange = false;

        private void SetListBoxSelectedItemsAfterEnumValueChange(Enum enumValue)
        {
            if (_updatingEnumValueAfterListBoxSelectionChange)
                return;
            _settingListBoxSelectedItems = true;

            if (TreatAsFlags)
            {
                ListBox listBox = SelectorObject as ListBox;
                listBox.SelectedItems.Clear();

                foreach (EnumSelectorDisplayItem item in FindItemsWithValue(enumValue))
                {
                    listBox.SelectedItems.Add(item);
                }
            }
            else
            {
                SelectorObject.SelectedItem = FindItemWithValue(enumValue);
            }

            _settingListBoxSelectedItems = false;
        }

        private void UpdateEnumValueAfterListBoxSelectionChange()
        {
            if (_settingListBoxSelectedItems)
                return;

            _updatingEnumValueAfterListBoxSelectionChange = true;

            if (TreatAsFlags)
            {
                EnumValue = GetEnumValueFromSelection((SelectorObject as ListBox).SelectedItems);
            }
            else
            {
                EnumValue = GetEnumValueFromSelection((EnumSelectorDisplayItem)SelectorObject.SelectedItem);
            }

            _updatingEnumValueAfterListBoxSelectionChange = false;
        }


        private EnumSelectorDisplayItem FindItemWithValue(Enum value)
        {
            if (DisplayItems == null) return null;

            foreach (EnumSelectorDisplayItem item in DisplayItems)
            {
                if (item.EnumValue != null && item.EnumValue.Equals(value))
                    return item;
            }
            return null;
        }

        private List<EnumSelectorDisplayItem> FindItemsWithValue(Enum value)
        {
            List<EnumSelectorDisplayItem> list = new List<EnumSelectorDisplayItem>();
            int valueInt = Convert.ToInt32(value, CultureInfo.InvariantCulture);
            foreach (EnumSelectorDisplayItem item in DisplayItems)
            {
                int itemValue = Convert.ToInt32(item.EnumValue, CultureInfo.InvariantCulture);
                if ( (valueInt | itemValue) == itemValue)
                    list.Add(item);
            }
            return list;
        }

        private Enum GetEnumValueFromSelection(IList selectedItems)
        {
            int value = 0;

            foreach (EnumSelectorDisplayItem item in selectedItems)
            {
                value |= Convert.ToInt32(item.EnumValue, CultureInfo.InvariantCulture);
            }

            return (Enum)Enum.ToObject(EnumType, value);
        }
        private static Enum GetEnumValueFromSelection(EnumSelectorDisplayItem selectedItem)
        {
            if(selectedItem != null)
                return selectedItem.EnumValue;
            return null;
        }


        #endregion

    }


}

